Skip to content

Module Systems Comparison: CommonJS vs RequireJS (AMD) vs ES6 Modules

🎯 THE Interview Question

"Can you explain the differences between CommonJS, AMD, and ES6 modules?"

This is a fundamental frontend/backend interview question that tests your understanding of JavaScript module systems evolution.

📋 Quick Answer Framework

Start with: "There are three main module systems in JavaScript, each solving different problems..."

  1. CommonJS - Server-side (Node.js), synchronous loading
  2. AMD (RequireJS) - Browser-side, asynchronous loading
  3. ES6 Modules - Native JavaScript, static analysis

🔍 Detailed Comparison

1. CommonJS (Node.js)

💡 Interview Gold: "CommonJS was designed for server environments where modules are loaded from the file system synchronously."

Syntax

javascript
// Exporting
module.exports = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b
};

// Or individual exports
exports.multiply = (a, b) => a * b;

// Importing
const math = require('./math');
const { add, subtract } = require('./math');

Key Characteristics

  • Synchronous loading - blocks until module is loaded
  • Dynamic imports - can require() conditionally
  • Runtime resolution - modules resolved at runtime
  • Not browser-native - needs bundler for browser use

🎯 Interview Tip: Mention that CommonJS is still the default in Node.js but ES6 modules are gaining adoption.


2. AMD (Asynchronous Module Definition) - RequireJS

💡 Interview Gold: "AMD was created to solve the asynchronous loading problem in browsers before ES6 modules existed."

Syntax

javascript
// Defining a module
define(['dependency1', 'dependency2'], function(dep1, dep2) {
  return {
    add: (a, b) => a + b,
    subtract: (a, b) => a - b
  };
});

// Requiring modules
require(['math', 'utils'], function(math, utils) {
  console.log(math.add(2, 3));
});

// With RequireJS config
require.config({
  paths: {
    'jquery': 'libs/jquery-3.6.0.min',
    'lodash': 'libs/lodash.min'
  }
});

Key Characteristics

  • Asynchronous loading - non-blocking in browsers
  • Dependency management - explicit dependency declaration
  • Browser-optimized - designed for web environments
  • Verbose syntax - more boilerplate code
  • Learning curve - additional concepts to master

🎯 Interview Tip: Explain that AMD was popular before ES6 modules but is now mostly legacy.


3. ES6 Modules (ESM)

💡 Interview Gold: "ES6 modules are the native JavaScript standard, providing static analysis and tree-shaking capabilities."

Syntax

javascript
// Named exports
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// Default export
export default class Calculator {
  add(a, b) { return a + b; }
}

// Mixed exports
export { multiply };
export default Calculator;

// Importing
import Calculator, { add, subtract } from './math.js';
import * as math from './math.js';

// Dynamic imports (ES2020)
const module = await import('./math.js');

Key Characteristics

  • Static analysis - imports/exports determined at compile time
  • Tree shaking - unused code can be eliminated
  • Native browser support - no bundler required (modern browsers)
  • Clean syntax - intuitive and readable
  • Hoisting - imports are hoisted to top
  • Strict mode only - always runs in strict mode

🎯 Interview Tip: Emphasize that ES6 modules are the future and enable better optimization.


🔄 Side-by-Side Example

Same Functionality, Different Systems

Math Utility Module

CommonJS:

javascript
// math.js
const PI = 3.14159;

function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}

module.exports = { PI, add, multiply };

// app.js
const { PI, add } = require('./math');
console.log(add(2, 3)); // 5

AMD (RequireJS):

javascript
// math.js
define(function() {
  const PI = 3.14159;
  
  return {
    PI: PI,
    add: function(a, b) { return a + b; },
    multiply: function(a, b) { return a * b; }
  };
});

// app.js
require(['math'], function(math) {
  console.log(math.add(2, 3)); // 5
});

ES6 Modules:

javascript
// math.js
export const PI = 3.14159;
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;

// app.js
import { PI, add } from './math.js';
console.log(add(2, 3)); // 5

📊 Performance & Bundle Size Impact

Tree Shaking Example

javascript
// utils.js - Large utility library
export const debounce = (fn, delay) => { /* implementation */ };
export const throttle = (fn, delay) => { /* implementation */ };
export const deepClone = (obj) => { /* implementation */ };
export const formatDate = (date) => { /* implementation */ };
// ... 50 more utilities

// app.js - Only using one function
import { debounce } from './utils.js';

// ✅ ES6 Modules: Only debounce code included in bundle
// ❌ CommonJS: Entire utils.js included in bundle
// ❌ AMD: Entire utils.js loaded at runtime

🎯 Interview Tip: "ES6 modules enable tree shaking, which can reduce bundle size by 30-50% in large applications."


🚀 Modern Usage Patterns

1. Node.js Evolution

javascript
// package.json
{
  "type": "module",  // Enable ES6 modules in Node.js
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  }
}

2. Browser Support

html
<!-- Modern browsers -->
<script type="module" src="app.js"></script>

<!-- Fallback for older browsers -->
<script nomodule src="app-legacy.js"></script>

3. Dynamic Imports (Modern)

javascript
// Code splitting with ES6 modules
const loadChart = async () => {
  const { Chart } = await import('./chart.js');
  return new Chart();
};

// Conditional loading
if (user.isAdmin) {
  const { AdminPanel } = await import('./admin.js');
  new AdminPanel().render();
}

❓ Common Interview Questions & Perfect Answers

Q1: "Why did AMD exist if we had CommonJS?"

Perfect Answer: "CommonJS was designed for server environments with synchronous file system access. In browsers, synchronous loading would block the UI thread, so AMD was created to provide asynchronous, non-blocking module loading specifically for browser environments."

Q2: "What are the main advantages of ES6 modules over CommonJS?"

Perfect Answer: "ES6 modules provide static analysis for better tooling and tree shaking, have cleaner syntax, are natively supported in browsers, enable better optimization through static imports, and are the official JavaScript standard."

Q3: "Can you use ES6 modules in Node.js?"

Perfect Answer: "Yes, Node.js supports ES6 modules since version 12. You can enable them by setting 'type': 'module' in package.json or using .mjs file extensions. However, CommonJS is still the default for backward compatibility."

Q4: "What is tree shaking and why is it important?"

Perfect Answer: "Tree shaking is the elimination of unused code during the build process. It's only possible with ES6 modules because their static structure allows bundlers to analyze which exports are actually used. This can significantly reduce bundle size in production."

Q5: "How do you handle circular dependencies in different module systems?"

Perfect Answer: "All three systems handle circular dependencies differently: CommonJS returns a partial export object, AMD can detect and warn about cycles, and ES6 modules handle them through live bindings. The best practice is to avoid circular dependencies through better architecture."

Q6: "What's the relationship between AMD and ES6 modules?"

Perfect Answer: "AMD was the stepping stone to ES6 modules. AMD solved asynchronous loading for browsers before ES6 existed. ES6 modules learned from AMD's strengths (async loading, browser focus) but added static analysis, cleaner syntax, and native browser support. Essentially, ES6 modules are AMD's modern successor."


🔄 Simple Evolution Story

🎯 Interview Gold: "Think of it as a 3-step evolution solving JavaScript's module problem:"

  1. 2009: CommonJS - "We need modules for Node.js servers" ✅
  2. 2011: AMD - "We need async modules for browsers" ✅
  3. 2015: ES6 - "We need ONE standard for everywhere" ✅

Key Point: Each system solved the previous one's main limitation:

  • CommonJS → AMD: Added async loading for browsers
  • AMD → ES6: Added static analysis + native support + cleaner syntax

🎯 Interview Tip: "ES6 modules are basically AMD done right with native browser support."


🎯 Interview Checklist

Must-Know Points:

  • [ ] CommonJS: Synchronous, Node.js default, require()/module.exports
  • [ ] AMD: Asynchronous, browser-focused, define()/require()
  • [ ] ES6 Modules: Static analysis, tree shaking, import/export
  • [ ] Performance: ES6 enables tree shaking and better optimization
  • [ ] Browser Support: ES6 modules work natively in modern browsers
  • [ ] Node.js: Supports both CommonJS (default) and ES6 modules

Bonus Points:

  • [ ] Mention dynamic imports for code splitting
  • [ ] Discuss bundle size optimization with tree shaking
  • [ ] Know about package.json "type": "module"
  • [ ] Understand circular dependency handling
  • [ ] Familiar with module federation concepts

🛠️ Quick Reference

FeatureCommonJSAMDES6 Modules
LoadingSynchronousAsynchronousStatic/Dynamic
EnvironmentNode.jsBrowserUniversal
Syntaxrequire()/module.exportsdefine()/require()import/export
Tree Shaking
Static Analysis
Browser Native
Bundle SizeLargerLargerSmaller
Current StatusLegacy/Node.jsLegacyModern Standard

🎯 Interview Gold: "ES6 modules are the future because they're the official standard, enable better optimization, and work natively in both browsers and Node.js."

Software Engineer Interview Preparation